#include "simple_c_lexer.h"
#include <iostream>

namespace{
	bool is_symbol(char c){
		return c == '(' || c == ')' || c == '{' || c == '}' 
			|| c == '[' || c == ']' || c == '!' || c == '~'
			|| c == '*' || c == ',' || c == '%' || c == '\\' 
			|| c == '?' || c == ':' || c == '^' || c == ';';
	}

	bool is_first_identifier_char(char c){
		return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
	}

	bool is_nth_identifier_char(char c){
		return is_first_identifier_char(c) || (c >= '0' && c <= '9');
	}

	bool is_oct_digit(char c){
		return c >= '0' && c <= '7';
	}

	bool is_dec_digit(char c){
		return c >= '0' && c <= '9';
	}

	bool is_hex_digit(char c){
		return is_dec_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
	}

	bool is_sign(char c){
		return c == '-' || c == '+';
	}

	bool is_numerical_suffix(char c){
		return c == 'u' || c == 'U' || c == 'l' || c == 'L';
	}

	bool is_whitespace(char c){
		return c == '\n' || c == '\r' || c == ' ' || c == '\t';
	}
}

simple_c_lexer::simple_c_lexer(const std::string &a_File)
	: m_Buffer(a_File.begin(), a_File.end()), m_BufferPos(0), m_LineNumber(0), m_Begin(0) {}

token simple_c_lexer::scan(){
	if(m_BufferPos >= m_Buffer.size() - 1){
		throw lexer_eof();
	}

	std::string lexeme = read_lexeme();

	if(lexeme.empty()) return scan();
	else return tokenize(lexeme);
}

token simple_c_lexer::make(simple_c_tokens t, const std::string &a_Lexeme){
	return token(t, a_Lexeme, m_Begin);
}

std::string simple_c_lexer::read_lexeme(){
	bool preprocessor = false;
	std::string lexeme = "";

	m_Begin = m_BufferPos;

	while(m_BufferPos < m_Buffer.size() - 1){
		char cur  = m_Buffer[m_BufferPos++];
		char peek = m_Buffer[m_BufferPos];

		if(cur == '/' && peek == '*'){
			// c style comments, ignore everything till "*/"
			lexeme += cur;

			while(m_BufferPos < m_Buffer.size() - 1){
				lexeme += m_Buffer[m_BufferPos];

				m_BufferPos++;
				if(m_Buffer[m_BufferPos - 2] == '*' && m_Buffer[m_BufferPos - 1] == '/') break;
				if(m_Buffer[m_BufferPos] == '\n') m_LineNumber++;
			}

			break;
		}else if (is_symbol(peek) || is_symbol(cur)){
			if(!is_whitespace(cur)){
				lexeme += cur;
			}
			break;
		}else if(cur == '#' || preprocessor){
			preprocessor = true;
			lexeme += cur;

			if(is_whitespace(peek)) break;
		}else if(cur == ' ' || cur == '\t'){
			break;
		}else if(cur == '\n' || cur == '\r'){
			m_LineNumber++;
			break;
		}else{
			lexeme += cur;
		}
	}

	return lexeme;
}

token simple_c_lexer::preprocessor_token(token &t, const std::string &lexeme){
	if(t.tag != kUndefined) return t;

	if(lexeme == "#define") return make(kDefine, lexeme);
	if(lexeme == "#else")	return make(kElse, lexeme);
	if(lexeme == "#ifndef") return make(kIfndef, lexeme);
	if(lexeme == "#ifdef")  return make(kIfdef, lexeme);
	if(lexeme == "#elif")   return make(kElif, lexeme);
	if(lexeme == "#endif")  return make(kEndif, lexeme);
	if(lexeme == "#if")		return make(kIf, lexeme);
	if(lexeme == "#include")return make(kInclude, lexeme);

	return t;
}

token simple_c_lexer::operator_token(token &t, const std::string &lexeme){
	if(t.tag != kUndefined) return t;

	if(lexeme == "!")  return make(kNot, lexeme);
	if(lexeme == "&&") return make(kLogicalAnd, lexeme);
	if(lexeme == "||") return make(kLogicalOr, lexeme);
	if(lexeme == "(")  return make(kOpenParentheses, lexeme);
	if(lexeme == ")")  return make(kCloseParentheses, lexeme);
	if(lexeme == ";")  return make(kEndOfStatement, lexeme);
	if(lexeme == "{")  return make(kOpenScope, lexeme);
	if(lexeme == "}")  return make(kCloseScope, lexeme);
	if(lexeme == "*")  return make(kStar, lexeme);
	if(lexeme == ",")  return make(kComma, lexeme);

	// comparison operators
	if(lexeme == "==") return make(kEquals, lexeme);
	if(lexeme == ">=") return make(kGreateEquals, lexeme);
	if(lexeme == "<=") return make(kLowerEquals, lexeme);
	if(lexeme == "!=") return make(kNotEquals, lexeme);
	if(lexeme == "<")  return make(kLower, lexeme);
	if(lexeme == ">")  return make(kGreater, lexeme);

	return t;
}

token simple_c_lexer::identifier_token(token &t, const std::string &lexeme){
	if(t.tag != kUndefined) return t;

	if(is_first_identifier_char(lexeme[0])){
		if(lexeme.size() > 1){
			// test the rest for alnum_
			for(std::string::size_type i = 0; i < lexeme.size(); i++){
				if(!is_nth_identifier_char(lexeme[i])) return t;
			}
			
			return make(kIdentifier, lexeme);
		}else{
			return make(kIdentifier, lexeme);
		}
	}

	return t;
}

token simple_c_lexer::numeral_token(token &t, const std::string &lexeme){
	if(t.tag != kUndefined) return t;

	// test numerical suffixes
	// currently we're accepting every combination of u, U, l and L

	std::string::size_type size = 0;

	if(!lexeme.empty()){
		size = lexeme.size() - 1;
		for(; size; size--){
			if(!is_numerical_suffix(lexeme[size])) break;
		}
	}

	if(lexeme.size() > 2 && lexeme[0] == '0' && (lexeme[1] == 'X' || lexeme[1] == 'x')){
		// parse hex number
		for(std::string::size_type i = 2; i < size; i++){
			if(!is_hex_digit(lexeme[i])) return t;
		}

		return make(kNumeralHex, lexeme);
	}else if(lexeme.size() > 1 && lexeme[0] == '0'){
		// parse octal number
		for(std::string::size_type i = 1; i < size; i++){
			if(!is_oct_digit(lexeme[i])) return t;
		}

		return make(kNumeralOct, lexeme);
	}else if(!lexeme.empty()){
		// parse decimal integer
		for(std::string::size_type i = is_sign(lexeme[0]); i < size; i++){
			if(!is_dec_digit(lexeme[i])) return t;
		}

		return make(kNumeralDec, lexeme);
	}

	return t;
}

token simple_c_lexer::include_string_token(token &t, const std::string &lexeme){
	if(t.tag != kUndefined) return t;

	if(lexeme.size() > 2 && lexeme[0] == '<' && lexeme[lexeme.size() - 1] == '>'){
		return make(kIncludeString, lexeme);
	}

	return t;
}

token simple_c_lexer::broken_string_token(token &t, const std::string &lexeme){
	if(t.tag != kUndefined) return t;

	if(lexeme.size() > 2 && lexeme[0] == '"' && lexeme[lexeme.size() - 1] == '"'){
		return make(kStringLiteral, lexeme);
	}else if(lexeme.size() > 2 && lexeme[0] == '\'' && lexeme[lexeme.size() - 1] == '\''){
		return make(kCharLiteral, lexeme);
	}

	return t;
}

token simple_c_lexer::comment_token(token &t, const std::string &lexeme){
	if(t.tag != kUndefined) return t;

	if(lexeme.size() > 4 && lexeme[0] == '/' && lexeme[1] == '*')
		return make(kComment, lexeme);

	return t;
}

token simple_c_lexer::tokenize(const std::string &lexeme){
	token t(kUndefined, "", -1);

	t = comment_token(t, lexeme);
	t = preprocessor_token(t, lexeme);
	t = operator_token(t, lexeme);
	t = numeral_token(t, lexeme);
	t = include_string_token(t, lexeme);
	t = broken_string_token(t, lexeme);

	t = identifier_token(t, lexeme);

	return t;
}